package com.hmall.search.service.impl;

import com.alibaba.fastjson.JSON;
import com.hmall.common.client.ItemClient;
import com.hmall.common.dto.Item;
import com.hmall.common.dto.PageDTO;
import com.hmall.search.pojo.ItemDoc;
import com.hmall.search.pojo.RequestParams;
import com.hmall.search.service.ItemSearchService;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestParam;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
public class ItemSearchServiceImpl implements ItemSearchService {

    @Autowired
    ItemClient itemClient;

    @Autowired
    RestHighLevelClient client;

    /**
     * 导数据到 es 中
     */
    @Override
    public void sync() {
        // 第一次查询总数
        PageDTO<Item> pageDTO = itemClient.list(1, 1);
        Long total = pageDTO.getTotal();
        // 遍历总数, 每次取 1000条 更新到es中
        Long totalPage = total / 1000;
        for (int i = 1; i <= totalPage + 1; i++) {
            PageDTO<Item> list = itemClient.list(i, 1000);
            List<Item> items = list.getList();
            // 导入到 es 中
            BulkRequest bulkRequest = new BulkRequest();
            for (Item item : items) {
                IndexRequest indexRequest = new IndexRequest("hamll");
                indexRequest.id(item.getId().toString());
                indexRequest.source(JSON.toJSONString(item), XContentType.JSON);
                bulkRequest.add(indexRequest);
            }
            try {
                client.bulk(bulkRequest, RequestOptions.DEFAULT);
                System.out.println("第" + i + "页 导入完成了!");
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 创建索引
     */
    @Override
    public void createIndex() {
        // 索引名称
        CreateIndexRequest request = new CreateIndexRequest("hamll");

        // 分片数 副本数
        request.settings(Settings.builder()
        .put("index.number_of_shards",1)
        .put("index.number_of_replicas",0)
        );
        // mapping  properties
        Map<String,Object> mapping = new HashMap<>();
        Map<String,Object> properties = new HashMap<>();
        // id
        Map<String,Object> id = new HashMap<>();
        id.put("type","keyword");
        properties.put("id",id);
        // all
        Map<String,Object> all = new HashMap<>();
        all.put("type","text");
        all.put("analyzer","ik_max_word");
        properties.put("all",all);
        // name
        Map<String,Object> name = new HashMap<>();
        name.put("type","text");
        name.put("analyzer","ik_max_word");
        name.put("copy_to","all");
        properties.put("name",name);
        // price
        Map<String,Object> price = new HashMap<>();
        price.put("type","long");
        properties.put("price",price);
        // stock
        Map<String,Object> stock = new HashMap<>();
        stock.put("type","integer");
        properties.put("stock",stock);
        //image
        Map<String,Object> image = new HashMap<>();
        image.put("type","keyword");
        image.put("index","false");
        properties.put("image",image);
        //category
        Map<String,Object> category = new HashMap<>();
        category.put("type","keyword");
        category.put("copy_to","all");
        properties.put("category",category);
        //brand
        Map<String,Object> brand = new HashMap<>();
        brand.put("type","keyword");
        brand.put("copy_to","all");
        properties.put("brand",brand);
        // spec
        Map<String,Object> spec = new HashMap<>();
        spec.put("type","keyword");
        properties.put("spec",spec);
        // sold
        Map<String,Object> sold = new HashMap<>();
        sold.put("type","integer");
        properties.put("sold",sold);
        // commentCount
        Map<String,Object> commentCount = new HashMap<>();
        commentCount.put("type","integer");
        properties.put("commentCount",commentCount);
        // status
        Map<String,Object> status = new HashMap<>();
        status.put("type","integer");
        properties.put("status",status);
        // isAD
        Map<String,Object> isAD = new HashMap<>();
        isAD.put("type","boolean");
        properties.put("isAD",isAD);
        // createTime
        Map<String,Object> createTime = new HashMap<>();
        createTime.put("type","date");
        properties.put("createTime",createTime);
        //updateTime
        Map<String,Object> updateTime = new HashMap<>();
        updateTime.put("type","date");
        properties.put("updateTime",updateTime);

        mapping.put("properties",properties);
        request.mapping(mapping);
        // 发送请求 添加索引库
        try {
            client.indices().create(request, RequestOptions.DEFAULT);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    /**
     * 过滤条件 加 聚合
     * @param params
     * @return
     */
    @Override
    public Map<String, List<String>> filters(RequestParams params) {
        try {
            //1. 准备 request
            SearchRequest request = new SearchRequest("hamll");
            //2. 准备 DSL
            //2.1 query
            buildBasicQuery(params, request);
            //2.2 设置 size
            request.source().size(0);
            //2.3 聚合
            buildAggregation(request);
            //3. 发送请求
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);
            //4. 解析结果
            Map<String, List<String>> result = new HashMap<>();
            Aggregations aggregations = response.getAggregations();
            //4.1 根据名称获取分类的结果
            List<String> categoryList = getAggByName(aggregations, "categoryAgg");
            result.put("category", categoryList);
            //4.2 根据名称获取品牌的结果
            List<String> brandList = getAggByName(aggregations, "brandAgg");
            result.put("brand", brandList);
            // 返回最终结果
            return result;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    // 聚合条件抽取
    private List<String> getAggByName(Aggregations aggregations, String aggName){
        //4.1 根据聚合名称获取聚合结果
        Terms brandTerms = aggregations.get(aggName);
        //4.2 获取 buckets
        List<? extends Terms.Bucket> buckets = brandTerms.getBuckets();
        //4.3 遍历
        ArrayList<String> brandList = new ArrayList<>();
        for (Terms.Bucket bucket : buckets) {
            //4.4 获取key
            String key = bucket.getKeyAsString();
            // 放入list
            brandList.add(key);
        }
        return brandList;
    }

    // 对聚合 条件 进行抽取
    private void buildAggregation(SearchRequest request){
        request.source().aggregation(AggregationBuilders
                .terms("categoryAgg")
                .field("category")
                .size(100));
        request.source().aggregation(AggregationBuilders
                .terms("brandAgg")
                .field("brand")
                .size(100));
    }

    // 条件过滤
    private void buildBasicQuery(RequestParams params, SearchRequest request){
        // 构建 booleanQuery
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        //1. 关键字搜索
        //2.1 query 拿前端传来的key
        String key = params.getKey();
        if (key == null || "".equals(key)){
            // 为空就查询全字段
            boolQuery.must(QueryBuilders.matchAllQuery());
        } else {
            // 不为空则根据条件查询
            boolQuery.must(QueryBuilders.matchQuery("all",key));
        }
        // 条件过滤 category
        if (params.getCategory() != null && !params.getCategory().equals("")){
            boolQuery.filter(QueryBuilders.termQuery("category",params.getCategory()));
        }
        // 条件过滤 brand
        if (params.getBrand() != null && !params.getBrand().equals("")){
            boolQuery.filter(QueryBuilders.termQuery("brand",params.getBrand()));
        }
        // 条件过滤 价格
        if (params.getMinPrice() != null && params.getMaxPrice() != null){
            // 大于 最小价格, 小于 最大价格
            boolQuery.filter(QueryBuilders.rangeQuery("price")
                    .gte(params.getMinPrice())
                    .lte(params.getMaxPrice()));
        }
        //准备 source
        request.source().query(boolQuery);

        //算分
        FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery(
                // 原始查询, 相关算分的查询
                boolQuery,
                // function score 的数组
                new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
                        // 其中一个 function score 元素
                        new FunctionScoreQueryBuilder.FilterFunctionBuilder(
                                // 过滤条件
                                QueryBuilders.termQuery("isAD", true),
                                // 算分函数
                                ScoreFunctionBuilders.weightFactorFunction(10)
                        )
                }
        );
        //广告排名 相关性算分, 优先己分值排序
        request.source().query(functionScoreQueryBuilder);
    }

    // 解析响应结果
    private PageDTO<ItemDoc> handleResponse(SearchResponse response){
        //4. 解析响应的数据 文档格式 -> json
        //4.1 拿到大的 Hits
        SearchHits searchHits = response.getHits();
        //4.2 获得总数据条数
        long total = searchHits.getTotalHits().value;
        //4.3 文档数组-> json
        SearchHit[] hits = searchHits.getHits();
        //4.4 遍历
        // 准备 集合装数据 返回给前端
        ArrayList<ItemDoc> items = new ArrayList<>();
        for (SearchHit hit : hits) {
            //获取文档source
            String json = hit.getSourceAsString();
            // 反序列化
            ItemDoc itemDoc = JSON.parseObject(json, ItemDoc.class);
            // 获取高亮结果
            Map<String, HighlightField> highlightFields =  hit.getHighlightFields();
            if (highlightFields != null) {
                // 根据字段名获取高亮
                HighlightField highlightField = highlightFields.get("name");
                if (highlightField != null) {
                    //获取高亮值
                    String name = highlightField.getFragments()[0].string();

                    //覆盖非高亮结果
                    itemDoc.setName(name);
                }
            }
            items.add(itemDoc);
        }
        //5. 封装返回结果
        return new PageDTO<>(total,items);
    }

    /**
     * 分页查询
     * @param params
     * @return
     */
    @Override
    public PageDTO<ItemDoc> list(RequestParams params) {
        try {
            //1. 准备 request
            SearchRequest request = new SearchRequest("hamll");
            //2. 准备 DSL
            // 拿到条件过滤
            buildBasicQuery(params, request);

            //3分页
            int page = params.getPage();
            int size = params.getSize();
            request.source().from((page - 1) * size).size(size);

            //4. 按照分值排序
            if (params.getSortBy().equals("default")){
                request.source().sort("_score", SortOrder.DESC);
            } else if (params.getSortBy().equals("sold")){
                request.source().sort(params.getSortBy(), SortOrder.DESC);
            } else {
                request.source().sort(params.getSortBy(), SortOrder.ASC);
            }

            // 关键字高亮
            if (!StringUtils.isEmpty(params.getKey())){
                HighlightBuilder highlightBuilder = new HighlightBuilder();
                highlightBuilder.field("name");
                // 不是必须配置搜索字段
                highlightBuilder.requireFieldMatch(false);
                request.source().highlighter(highlightBuilder);
            }

            //5. 发送请求
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);
            //6. 解析响应
            return handleResponse(response);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}
